﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using Bling.DSL;
using Bling.Shapes;
using Bling.Core;
using Bling.WPF;
using Bling.Util;


namespace BookFlipDemo {
  class FlipPage : Canvas {

    public Canvas frontPageCav;
    private FrameworkElement frontPage;
    private Canvas backPageCav;
    private FrameworkElement backPage;

    public Line CutLine;

    public PointBl CutLineDir;
    public PointBl CutLineDirP;
    public PointBl CutLineCenter;    

    public List<Point> AnchorPoints = new List<Point>();

    public bool EnableAssistLine = false;

    public Canvas backShadowCav;
    private Rectangle backShadow;

    public Canvas frontShadowCav;
    private Rectangle frontShadow;

    private Canvas outShadowCav;
    private Rectangle outShadow;
    
    public FrameworkElement FrontPage {
      get {
        return frontPage;
      }
    }

    public FrameworkElement BackPage {
      get {
        return backPage;
      }
    }

    public bool IsFlipping = false;

    public static readonly GetProperty<FlipPage,Point> 
      CutLineStartProperty = "CutLineStart".NewProperty<FlipPage, Point>();
    public static readonly GetProperty<FlipPage,Point> CutLineEndProperty = "CutLineEnd".NewProperty<FlipPage, Point>();
    public static readonly GetProperty<FlipPage,Point> RealFlipPointProperty = "RealFlipPoint".NewProperty<FlipPage, Point>();
    public static readonly GetProperty<FlipPage,Point> StartPointProperty = "StartPoint".NewProperty<FlipPage, Point>();
    //public static readonly TypedDependencyProperty<FlipPage, Point> FlipPointProperty = "FlipPoint".RegisterDp<FlipPage, Point>();

    public PointBl CutLineStart {
      get { return CutLineStartProperty[this]; }
      set { CutLineStart.Bind = value; }
    }
    public PointBl CutLineEnd {
      get { return CutLineEndProperty[this]; }
      set { CutLineEnd.Bind = value; }
    }
    public PointBl RealFlipPoint {
      get { return  RealFlipPointProperty[this]; }
      set { RealFlipPoint.Bind = value; }
    }

    //public PointBl FlipPoint {
    //  get { return FlipPointProperty.Bl(this); }
    //  set { FlipPoint.Bind = value; }
    //}

    public PointBl StartPoint {
      get { return StartPointProperty[this]; }
      set { StartPoint.Bind = value; }
    }

    private ThumbBl startPointThumb;

    private Geometry innerClip;
    private Geometry outerClip;

    private Geometry backPageGeometry;
    private Geometry frontPageGeometry;
    
    public FlipPage(FrameworkElement frontPage, FrameworkElement backPage, bool samePage ) {
      frontPageCav = new Canvas();
      frontPageCav.Bl().LeftTop = new Point(0, 0);
      frontPageCav.Bl().Size = this.Bl().Size;
      frontPageCav.Bl().ZIndex = 0;

      this.frontPage = frontPage;
      frontPage.Bl().LeftTop = new Point(0, 0);
      frontPage.Bl().Size = this.Bl().Size;
      //frontPage.Bl().ZIndex = 0;
      frontPage.Bl().RenderTransform.Rotate = (0.0).Bl().ToDegrees();
      frontPage.Bl().Visibility = Visibility.Visible;
      //var frontShadowEffect = new DropShadowEffect() { Opacity = 0.3, ShadowDepth = 15 };
      //frontShadowEffect.Direction = -75;
      //frontPage.Effect = frontShadowEffect;

      frontPageCav.Children.Add(frontPage);
      this.Children.Add(frontPageCav);

      backPageCav = new Canvas();
      backPageCav.Bl().LeftTop = frontPageCav.Bl().LeftTop;
      backPageCav.Bl().Size = this.Bl().Size;
      backPageCav.Bl().ZIndex = frontPageCav.Bl().ZIndex + 3;      
      this.Children.Add(backPageCav);

      //backPage.Bl().Scale = new PointBl(-1, 1);
      this.backPage = backPage;
      backPage.Bl().LeftTop = new Point(0, 0);
      backPage.Bl().Size = this.Bl().Size;
      backPage.Bl().RenderTransform.Rotate = frontPage.Bl().RenderTransform.Rotate;

      backPage.Bl().Visibility = Visibility.Hidden;
      if (samePage) {
        backPage.Bl().RenderTransform.Scale = new PointBl(-1, 1);
      }

      backPageCav.Children.Add(backPage);

      RealFlipPoint = new PointBl(0, 0);
      StartPoint = new PointBl(0.1, 0.1);
      CutLineDirP = (RealFlipPoint - StartPoint).Normalize;
      CutLineDir = new PointBl(CutLineDirP.Y, -CutLineDirP.X); //CutLineDirP is perpendicular to CutLineDir, in clockwise
      CutLineCenter = (RealFlipPoint + StartPoint) / 2;
      var maxDistance = this.frontPage.Bl().Size.Length;
      CutLineStart = CutLineCenter - maxDistance * CutLineDir;
      CutLineEnd = CutLineCenter + maxDistance * CutLineDir;

      //FindRealFlipPoint2();

      ApplyShadow();

      //
      // Apply flipping
      //
      ApplyFlip();

      //
      // Clipping geometry
      //
      //var wLeftTop = backPage.Bl().LeftTop.Rotate(backPage.Bl().CenterPosition, (backPage.Bl().RenderTransform.Rotate + (90.0).Bl().ToDegrees).ToRadians);
      //var wRightTop = backPage.Bl().RightTop.Rotate(backPage.Bl().CenterPosition, (backPage.Bl().RenderTransform.Rotate + (90.0).Bl().ToDegrees).ToRadians);
      //var wRightBottom = backPage.Bl().RightBottom.Rotate(backPage.Bl().CenterPosition, (backPage.Bl().RenderTransform.Rotate + (90.0).Bl().ToDegrees).ToRadians);
      //var wLeftBottom = backPage.Bl().LeftBottom.Rotate(backPage.Bl().CenterPosition, (backPage.Bl().RenderTransform.Rotate + (90.0).Bl().ToDegrees).ToRadians);

      var wLeftTop = backPage.Bl().LeftTop + backPage.Bl().RenderTransform.Transform(new PointBl( 0,0) );
      var wRightTop = backPage.Bl().LeftTop + backPage.Bl().RenderTransform.Transform(new PointBl(backPage.Bl().Width, 0));
      var wRightBottom = backPage.Bl().LeftTop + backPage.Bl().RenderTransform.Transform(new PointBl(backPage.Bl().Width, backPage.Bl().Height));
      var wLeftBottom = backPage.Bl().LeftTop + backPage.Bl().RenderTransform.Transform(new PointBl(0, backPage.Bl().Height));
#if false
      var thumb1 = new ThumbBl(this) { Background = Brushes.Red, ZIndex = 100 };
      thumb1.CenterPosition = wLeftTop;

      var thumb2 = new ThumbBl(this) { Background = Brushes.Yellow, ZIndex = 100 };
      thumb2.CenterPosition = wRightTop;

      var thumb3 = new ThumbBl(this) { Background = Brushes.Blue, ZIndex = 100 };
      thumb3.CenterPosition = wRightBottom;

      var thumb4 = new ThumbBl(this) { Background = Brushes.Green, ZIndex = 100 };
      thumb4.CenterPosition = wLeftBottom;
#endif

      var bgPoints = new PointBl[] { wLeftTop, wRightTop, wRightBottom, wLeftBottom };
      backPageGeometry = CreatePathGeometry(bgPoints);

      //frontPageGeometry = new RectangleGeometry(new Rect(frontPage.Bl().LeftTop.Value, (frontPage.Bl().LeftTop + frontPage.Bl().Size).Value));
      var fgPoints = new PointBl[] { frontPage.Bl().LeftTop, frontPage.Bl().RightTop, frontPage.Bl().RightBottom, frontPage.Bl().LeftBottom };
      frontPageGeometry = CreatePathGeometry(fgPoints);

      CutLine = new Line();
      CutLine.Stroke = Brushes.Red;
      CutLine.Bl().ZIndex = 10;
      CutLine.Visibility = Visibility.Hidden;
      Children.Add(CutLine);

      startPointThumb = new ThumbBl(this);
      startPointThumb.Background = Brushes.Purple;
      startPointThumb.Visibility = Visibility.Hidden;
      startPointThumb.ZIndex = 11;
      startPointThumb.CenterPosition = StartPoint;
      
    }


    private void ApplyShadow() {

      //
      // Back Shadows
      //
      backShadowCav = new Canvas();
      backShadowCav.Bl().LeftTop = frontPageCav.Bl().LeftTop;
      backShadowCav.Bl().ZIndex = backPageCav.Bl().ZIndex + 1;
      backShadowCav.Bl().Size = frontPageCav.Bl().Size;
      this.Children.Add(backShadowCav);

      backShadow = new Rectangle();
      backShadowCav.Children.Add(backShadow);
      var maxDistance = this.frontPage.Bl().Size.Length; //(CutLineEnd - CutLineStart).Length;
      backShadow.Bl().CenterPosition = CutLineCenter;// (cutLineEnd + cutLineStart) / 2;
      backShadow.Bl().RenderTransform.Rotate = CutLineDir.Angle.AsDegrees;
      backShadow.Bl().Width = (CutLineEnd - CutLineStart).Length;

      var lineC = -CutLineDirP.Dot(CutLineStart);
      var distance = (RealFlipPoint.Dot(CutLineDirP) + lineC);
      backShadow.Bl().Height = (distance / maxDistance) * this.frontPage.Bl().Width / 2;


      var backShadowBrush = new LinearGradientBrush();
      backShadowBrush.StartPoint = new Point(0.5, 0.5);
      backShadowBrush.EndPoint = new Point(0.5, 1.0);
      backShadowBrush.SpreadMethod = GradientSpreadMethod.Reflect;

      backShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(100, 0, 0, 0), 0.0));
      //backShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(0, 0, 0, 0), 1.0));
      backShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(64, 0, 0, 0), 0.3));
      backShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(32, 160, 160, 160), 0.7));
      backShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(0, 160, 160, 160), 1.0));

      backShadow.Fill = backShadowBrush;

      //
      // Out shadow
      //
      outShadowCav = new Canvas();
      outShadowCav.Bl().LeftTop = frontPageCav.Bl().LeftTop;
      outShadowCav.Bl().ZIndex = backPageCav.Bl().ZIndex - 1;
      outShadowCav.Bl().Size = frontPageCav.Bl().Size;
      this.Children.Add(outShadowCav);

      outShadow = new Rectangle();
      outShadowCav.Children.Add(outShadow);
      outShadow.Bl().CenterPosition = backShadow.Bl().CenterPosition;
      outShadow.Bl().RenderTransform.Rotate = backShadow.Bl().RenderTransform.Rotate;
      outShadow.Bl().Width = backShadow.Bl().Width;
      var outMaxDistance = maxDistance / 2;
      var distance2 = (outMaxDistance / 2 - (distance - outMaxDistance / 2).Abs);
      outShadow.Bl().Height = (distance2 / outMaxDistance) * this.frontPage.Bl().Width / 1.5;


      var outShadowBrush = new LinearGradientBrush();
      outShadowBrush.StartPoint = new Point(0.5, 0.5);
      outShadowBrush.EndPoint = new Point(0.5, 1.0);
      outShadowBrush.SpreadMethod = GradientSpreadMethod.Reflect;

      outShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(70, 0, 0, 0), 0.0));
      outShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(0, 0, 0, 0), 1.0));
      //backShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(64, 0, 0, 0), 0.4));
      //backShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(32, 160, 160, 160), 0.7));
      //backShadowBrush.GradientStops.Add(new GradientStop(Color.FromArgb(0, 160, 160, 160), 1.0));

      outShadow.Fill = outShadowBrush;

      //
      // Front shadow
      //
      frontShadowCav = new Canvas();
      frontShadowCav.Bl().LeftTop = frontPageCav.Bl().LeftTop;
      frontShadowCav.Bl().ZIndex = backPageCav.Bl().ZIndex - 1;
      frontShadowCav.Bl().Size = frontPageCav.Bl().Size;

      this.Children.Add(frontShadowCav);

      frontShadow = new Rectangle();

      frontShadowCav.Children.Add(frontShadow);

      frontShadow.Fill = new SolidColorBrush(Color.FromArgb(64, 0, 0, 0));
      frontShadow.Bl().Size = backPage.Bl().Size;
      //frontShadow.Bl().ScaleAt(frontShadow.Bl().CenterSize).Bind = new PointBl(1.05, 1.05);
      var backPageAngle = backPage.Bl().RenderTransform.Rotate;
      frontShadow.Bl().RenderTransform.Rotate = backPageAngle;
      frontShadow.Bl().RenderTransform.Scale = backPage.Bl().RenderTransform.Scale;
      
      frontShadow.Bl().CenterPosition = backPage.Bl().CenterPosition + 25 * ((45.0).Bl().ToDegrees() - backPageAngle*backPage.Bl().RenderTransform.Scale.X ).ToPoint;

      //frontShadow.Bl().Visibility = backPage.Bl().Visibility;
      frontShadow.Bl().Visibility = Visibility.Hidden;

    }

    private void ApplyFlip() {

      //
      // Locating backPage 
      //
      var va = new PointBl(0, -1);
      
      //var halfAngle = CalcAngle(lineDir, va);
      var halfAngle = va.AngleBetween(CutLineDir).AsDegrees;
      backPage.Bl().RenderTransform.Rotate = ( backPage.Bl().RenderTransform.Scale.X * 2.0 * halfAngle.Degrees).ToDegrees();
      backPage.Bl().CenterPosition = FindMirrorPoint(frontPage.Bl().CenterPosition, CutLineCenter, CutLineCenter + CutLineDir);

      //
      // clip
      //
      var maxLen = 2 * this.frontPage.Bl().Size.Length;
      var outerPoints = new PointBl[4];
      outerPoints[0] = CutLineCenter + maxLen / 2 * CutLineDir;
      outerPoints[1] = outerPoints[0] - maxLen * CutLineDirP;
      outerPoints[2] = outerPoints[1] - maxLen * CutLineDir;
      outerPoints[3] = outerPoints[2] + maxLen * CutLineDirP;
      outerClip = CreatePathGeometry(outerPoints);

      var innerPoints = new PointBl[4];
      innerPoints[0] = CutLineCenter - maxLen / 2 * CutLineDir;
      innerPoints[1] = innerPoints[0] + maxLen * CutLineDirP;
      innerPoints[2] = innerPoints[1] + maxLen * CutLineDir;
      innerPoints[3] = innerPoints[2] - maxLen * CutLineDirP;
      innerClip = CreatePathGeometry(innerPoints);

    }


    public bool StartFlip( out Point realFlipPoint, PointBl testCenter, PointBl flipPoint) {
      
      realFlipPoint = new Point(0, 0);
      //
      // find start Point
      //
      //var center = frontPage.Bl().CenterSize;
      var center = testCenter;
      if (flipPoint.Equals(center))
        return false;

      var dir1 = ((flipPoint - center)).Normalize;

      Point[] endPoints;
      if (!FindLineRectIntersect(out endPoints, dir1, center, frontPage.Bl().LeftTop, frontPage.Bl().RightBottom))
        return false;

      PointBl mirrorPoint;
      if (((endPoints[0].Bl() - center).Normalize.Dot(dir1) - 1.0).Abs.CurrentValue <= 0.0001)
        mirrorPoint = endPoints[0];
      else
        mirrorPoint = endPoints[1];

      StartPoint = mirrorPoint;

      //
      // if can be flipped according to the flip point and start point
      //

      if (FindRealFlipPoint(out realFlipPoint, flipPoint)) {

        Rect edgeRect = new Rect(StartPoint.CurrentValue.X - 0.0001, StartPoint.CurrentValue.Y - 0.0001, 0.0002, 0.0002);

        if (!edgeRect.Contains(realFlipPoint)) {

          RealFlipPoint.Now = realFlipPoint.Bl();
        } else {
          realFlipPoint = (Point) RealFlipPoint;
        }

        IsFlipping = true;


        frontPageCav.Clip = innerClip;
        backPageCav.Clip = innerClip;

        //
        // backShadow rectangle
        //
        backShadowCav.Clip = new CombinedGeometry(GeometryCombineMode.Intersect, backPageGeometry, innerClip);

        outShadowCav.Clip = new CombinedGeometry(GeometryCombineMode.Intersect, frontPageGeometry, outerClip);

        //;
        // frontShadow rectangle
        //
        frontShadowCav.Clip = new CombinedGeometry(GeometryCombineMode.Intersect,
          frontPageGeometry,
          innerClip);
        //frontShadowCav.Clip = innerClip;

        return true;
      }

      IsFlipping = false;

      return false;
    }



    public bool Flip(PointBl flipPoint) {

      Point realFlipPoint = (Point) flipPoint;

      if (!FindRealFlipPoint(out realFlipPoint, flipPoint.CurrentValue))
        return false;

      Rect edgeRect = new Rect(StartPoint.CurrentValue.X - 0.0001, StartPoint.CurrentValue.Y - 0.0001, 0.0002, 0.0002);

      if ( !edgeRect.Contains(realFlipPoint) )       
        RealFlipPoint.Now = realFlipPoint.Bl();

      if (EnableAssistLine) {
        CutLine.X1 = CutLineStart.X.CurrentValue;
        CutLine.Y1 = CutLineStart.Y.CurrentValue;
        CutLine.X2 = CutLineEnd.X.CurrentValue;
        CutLine.Y2 = CutLineEnd.Y.CurrentValue;

        CutLine.Visibility = Visibility.Visible;

        startPointThumb.Visibility = Visibility.Visible;
      }


      backPage.Bl().Visibility = Visibility.Visible;

      backShadow.Bl().Visibility = Visibility.Visible;
      frontShadow.Bl().Visibility = Visibility.Visible;
      outShadow.Bl().Visibility = Visibility.Visible;

      return true;
    }

    public void StopFlip() {

      IsFlipping = false;
      //this.Clip = null;
      frontPageCav.Clip = null;
      backPageCav.Clip = null;

      backPage.Bl().Visibility = Visibility.Hidden;
      backShadow.Bl().Visibility = Visibility.Hidden;
      frontShadow.Bl().Visibility = Visibility.Hidden;
      outShadow.Bl().Visibility = Visibility.Hidden;

      if (EnableAssistLine) {
        CutLine.Visibility = Visibility.Hidden;
        startPointThumb.Visibility = Visibility.Hidden;
      }

    }


    private bool FindRealFlipPoint(out Point result, PointBl flipPoint) {
      var lineDirP = (flipPoint - StartPoint).Normalize;
      var centerPoint = (flipPoint + StartPoint) / 2;

      //lineDir is the direction of the cut line
      var lineDir = new Point(lineDirP.CurrentValue.Y, -lineDirP.CurrentValue.X);
      var farestAnchorPoint = new PointBl(0, 0);
      double maxDistance = Double.MinValue;
      var hiddenAnchors = new List<Point>();
      double len = 0;

      result = new Point(0,0);

      //Console.WriteLine("line dir in flipping=" + lineDir);
      //Console.WriteLine("start point=" + startPoint);

      //
      // find whether there are anchor points clipped by flip line.
      //
      foreach (var anchorPoint in this.AnchorPoints) {
        if (CrossProduct2D(anchorPoint - ((Vector) centerPoint), lineDir) >= 0) {

          len = (anchorPoint.Bl() - StartPoint).Length.CurrentValue;

          if (len.Bl().Abs.CurrentValue <= 0.00001) {
            return false;
          }

          var lineC = -lineDirP.Dot(centerPoint);
          var distance = -(anchorPoint.Bl().Dot(lineDirP) + lineC).CurrentValue;

          if (distance >= maxDistance) {
            maxDistance = distance;
            farestAnchorPoint = anchorPoint;
          }

          hiddenAnchors.Add(anchorPoint);
        }
      }
      //Console.WriteLine("hidden anchor num=" + hiddenAnchors.Count);

      if (hiddenAnchors.Count == 0) {
        result = ((Point) flipPoint);
        return true;
      }

      len = (farestAnchorPoint - StartPoint).Length.CurrentValue;
        
      var realFlipPoint = farestAnchorPoint + len * (flipPoint - farestAnchorPoint).Normalize;
      var lineDirP2 = (realFlipPoint - StartPoint).Normalize;
      var centerPoint2 = (realFlipPoint + StartPoint) / 2;
      var lineDir2 = new Point(lineDirP2.CurrentValue.Y, -lineDirP2.CurrentValue.X);

      if (!IsHidePoints(farestAnchorPoint, farestAnchorPoint + lineDir2.Bl() , AnchorPoints.ToArray())) {
        //FlippingInternal(realFlipPoint);
        //Console.WriteLine("split by one point!" + farestAnchorPoint.Value + "," + realFlipPoint.Value);

        result = ((Point) realFlipPoint);
        return true;
      }

      if (hiddenAnchors.Count == 1) {
        return false;
      }

      //
      //hiddenAnchors.Count >= 2
      //
      Point[] anchorArray = hiddenAnchors.ToArray();

      bool findLine = false;
      for (int i = 0; i < anchorArray.Length; i++) {
        PointBl lineStartPoint;
        PointBl lineEndPoint;
        if (i == anchorArray.Length - 1) {
          lineStartPoint = anchorArray[i];
          lineEndPoint = anchorArray[0];
        } else {
          lineStartPoint = anchorArray[i];
          lineEndPoint = anchorArray[i + 1];
        }

        if ((lineEndPoint - lineStartPoint).Dot(lineDir).CurrentValue < 0) {
          var t = lineStartPoint;
          lineStartPoint = lineEndPoint;
          lineEndPoint = t;
        }

        if (anchorArray.Length == 2) {
          findLine = true;
        } else {
          //findLine = true;
          findLine = !IsHidePoints(lineStartPoint, lineEndPoint, AnchorPoints.ToArray());

        }

        if (findLine) {
          //Console.WriteLine("flip line start=" + lineStartPoint.Value + ",end=" + lineEndPoint.Value);

          realFlipPoint = FindFlipPointWithLine(StartPoint, flipPoint, lineStartPoint, lineEndPoint);

          //Console.WriteLine("split by line!" + "start=" + lineStartPoint.Value + ",end=" + lineEndPoint + ",flip pos=" + realFlipPoint);
          result = ((Point) realFlipPoint);
          return true;
        }
      }//end of foreach line

      return false;
    }

    private PointBl FindMirrorPoint(PointBl origPoint, PointBl lineStart, PointBl lineEnd) {
      var lineDir = (lineEnd - lineStart).Normalize;
      
      var lineDirP = new PointBl(-lineDir.Y, lineDir.X);
      var lineC = -lineDirP.Dot(lineStart);

      var distance = (origPoint.Dot(lineDirP) + lineC);

      var mirrorPoint = origPoint - (2 * distance) * lineDirP;

      return mirrorPoint;

    }

    
    private PointBl FindFlipPointWithLine(PointBl p, PointBl targetPoint, PointBl lineStart, PointBl lineEnd) {
      var lineDir = (lineEnd - lineStart).Normalize;
      if (CrossProduct2D(p - lineStart, lineDir) * CrossProduct2D(targetPoint - lineStart, lineDir) >= 0)
        return p;
      
      return FindMirrorPoint(p, lineStart, lineEnd);
    }

    private bool IsHidePoints(PointBl lineStartPoint, PointBl lineEndPoint, Point[] points) {
      foreach (var p2 in points) {
        if ((!p2.Equals(lineEndPoint)) && (!p2.Equals(lineStartPoint))) {
          if (CrossProduct2D(p2.Bl() - lineStartPoint, lineEndPoint - lineStartPoint) > 0) {
            return true;
          }
        }
      }
      return false;
    }

    //
    // va x vb in counter clockwise right handed order
    //
    private double CrossProduct2D(PointBl va, PointBl vb) {
      //return (va.X * vb.Y - va.Y * vb.X).Value;
      return va.Cross(vb).CurrentValue;
    }


    private PointBl FindLineIntersect(PointBl dir1, PointBl ref1, PointBl dir2, PointBl ref2) {
      if ((dir2.Y * dir1.X - dir2.X * dir1.Y).Abs.CurrentValue <= 0.0001) //if dir1 is parallel to dir2
        return new Point(0, 0);

      var ua = (dir2.X * (ref1.Y - ref2.Y) - dir2.Y * (ref1.X - ref2.X)) / (dir2.Y * dir1.X - dir2.X * dir1.Y);
      //ub = (dir1.X * (ref1.Y - ref2.Y) - dir1.Y * (ref1.X - ref2.X)) / (dir2.Y * dir1.X - dir2.Y * dir1.Y);

      return (ref1 + (ua * dir1));
    }


    private PointBl FindLineIntersect2(Point dir1, Point ref1, Point dir2, Point ref2) {
      if ((dir2.Y * dir1.X - dir2.X * dir1.Y).Bl().Abs.CurrentValue <= 0.001)
        return new Point(0, 0);

      double ua, ub;

      ua = (dir2.X * (ref1.Y - ref2.Y) - dir2.Y * (ref1.X - ref2.X)) / (dir2.Y * dir1.X - dir2.X * dir1.Y);
      ub = (dir1.X * (ref1.Y - ref2.Y) - dir1.Y * (ref1.X - ref2.X)) / (dir2.Y * dir1.X - dir2.Y * dir1.Y);

      return ref1.Bl() + (ua * dir1.Bl());
    }


    private bool IsInsideLine(PointBl p1, PointBl p2, PointBl testP) {
      if (((testP - p1).Normalize.Dot((p2 - p1).Normalize) - 1.0).Abs.CurrentValue <= 0.001) {
        if ((testP - p1).Length.CurrentValue <= (p2 - p1).Length.CurrentValue)
          return true;
      }

      return false;
    }

    private bool FindLineRectIntersect(out Point[] endPoints, PointBl lineDir, PointBl refPoint, PointBl pLeftTop, PointBl pRightBottom) {
      endPoints = new Point[2];

      var pLeftBottom = pLeftTop + new PointBl(0, pRightBottom.Y - pLeftTop.Y);
      var pRightTop = pLeftTop + new PointBl(pRightBottom.X - pLeftTop.X, 0);

      var pTop = FindLineIntersect2(new Point(1, 0), ((Point) pLeftTop), ((Point) lineDir), ((Point) refPoint));
      var pLeft = FindLineIntersect2(new Point(0, -1), ((Point) pLeftBottom), ((Point) lineDir), ((Point) refPoint));
      var pBottom = FindLineIntersect2(new Point(-1, 0),((Point)  pRightBottom), ((Point) lineDir), ((Point) refPoint));
      var pRight = FindLineIntersect2(new Point(0, 1), ((Point) pRightTop), ((Point) lineDir), ((Point) refPoint));

      if (lineDir.Y.Abs.CurrentValue <= 0.0001) { //if parallel to x axis
        if (refPoint.Y.CurrentValue < pLeftTop.Y.CurrentValue || refPoint.Y.CurrentValue > pRightBottom.Y.CurrentValue)
          return false;

        endPoints[0] = new Point(pLeftTop.X.CurrentValue, refPoint.Y.CurrentValue);
        endPoints[1] = new Point(pRightBottom.X.CurrentValue, refPoint.Y.CurrentValue);

      } else if (lineDir.X.Abs.CurrentValue <= 0.0001) { //if parallel to y axis
        if (refPoint.X.CurrentValue < pLeftTop.X.CurrentValue || refPoint.X.CurrentValue > pRightBottom.X.CurrentValue)
          return false;

        endPoints[0] = new Point(refPoint.X.CurrentValue, pLeftTop.Y.CurrentValue);
        endPoints[1] = new Point(refPoint.X.CurrentValue, pRightBottom.Y.CurrentValue);
      } else {
        int i = 0;

        if ((i < 2) && IsInsideLine(pLeftTop, pRightTop, pTop))
          endPoints[i++] = (Point) pTop;
        if ((i < 2) && IsInsideLine(pRightTop, pRightBottom, pRight))
          endPoints[i++] = (Point) pRight;
        if ((i < 2) && IsInsideLine(pRightBottom, pLeftBottom, pBottom))
          endPoints[i++] = (Point) pBottom;
        if ((i < 2) && IsInsideLine(pLeftBottom, pLeftTop, pLeft))
          endPoints[i++] = (Point) pLeft;

        if (i == 0)
          return false;

        if (i == 1) {
          endPoints[1] = new Point(endPoints[0].X + 0.001, endPoints[1].Y + 0.001);
          Console.WriteLine("Ooops! same point!");

        }

      }

      return true;
    }

    private PathGeometry CreatePathGeometry(PointBl[] points) {
      var geometry = new PathGeometry();
      var figure = new PathFigureBl();
      geometry.Bl().Figures.Add(figure);
      figure.StartPoint = points[0];
      for (int i = 1; i < points.Length; i++) {
        points[i].Segment(figure);
      }

      return geometry;
    }

    public bool IsPointInsideFlip( PointBl point ) {

      return CrossProduct2D(point - CutLineCenter, CutLineDir) < 0;       
    }
  }
}